package com.demo.servicelmpl;

import com.demo.bean.*;
import com.demo.mapper.*;
import com.demo.service.EventManageService;
import com.demo.RemoteCall.TaskManageRemoteCallService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;

/**
 * @author 李天航
 * 活动安排实现类
 */
@Service
public class EventManagerServiceImpl implements EventManageService {

    @Resource
    LessonMapper lessonMapper;
    @Resource
    FixedEventsMapper fixedEventsMapper;
    @Resource
    TaskMapper taskMapper;
    @Resource
    DailyActivityMapper dailyActivityMapper;
    @Resource
    ResultMapper resultMapper;
    @Resource
    TaskManageRemoteCallService taskManageRemoteCallService;
    /**
     * 周一
     */
    final int WEEKBEGIN=1;
    /**
     * 周日
     */
    final int WEEKEND=7;
    /**
     * 一般人的睡觉开始时间
     */
    final long SLEEPBEGIN=23*60;
    /**
     * 一般人的睡觉结束时间
     */
    final long SLEEPEND=7*60;

    @AllArgsConstructor
    private static class TaskAndVis{
        Task task;
        boolean vis;
    }

    /**
     * 不忽略日常活动后的活动组装
     * @param weekNum 周数
     * @param lessons 课程表
     * @param fixedEvents 固定活动
     * @param dailyActivities 日常活动
     * @return 组装后的活动表
     */
    List<FixedEvent> getFixedEvents(long weekNum,List<Lesson> lessons,List<FixedEvent> fixedEvents,List<DailyActivity> dailyActivities)
    {
        List<FixedEvent> alreadyIn = new ArrayList<>();
        //将已经固定的活动统一转换成以周一为开始的时间段
        for(Lesson lesson:lessons)
        {
            alreadyIn.add(new FixedEvent(lesson,weekNum));
        }
        alreadyIn.addAll(fixedEvents);
        for(int i=WEEKBEGIN;i<=WEEKEND;i++)
        {
            for(DailyActivity dailyActivity:dailyActivities)
            {
                alreadyIn.add(new FixedEvent(dailyActivity,weekNum,i));
            }
        }
        alreadyIn.sort((o1, o2) -> Long.compare(o1.getBeginTime(), o2.getBeginTime()));
        return alreadyIn;
    }

    /**
     * 保留睡觉时间，忽略其他的日常活动后的活动组装
     * @param weekNum 周数
     * @param lessons 课程表
     * @param fixedEvents 固定活动
     * @param dailyActivities 日常活动
     * @return 组装后的活动表
     */
    List<FixedEvent> getFixedEventsIgnoreDailyActivity(long weekNum,List<Lesson> lessons,List<FixedEvent> fixedEvents,List<DailyActivity> dailyActivities)
    {
        List<FixedEvent> alreadyIn = new ArrayList<>();
        //将已经固定的活动统一转换成以周一为开始的时间段
        for(Lesson lesson:lessons) {
            alreadyIn.add(new FixedEvent(lesson,weekNum));
        }
        alreadyIn.addAll(fixedEvents);
        for(int i=WEEKBEGIN;i<=WEEKEND;i++)
        {
            for(DailyActivity dailyActivity:dailyActivities)
            {
                long beginTime=dailyActivity.getBeginTime();
                if(beginTime>=SLEEPBEGIN||beginTime<=SLEEPEND) {
                    alreadyIn.add(new FixedEvent(dailyActivity, weekNum, i));
                }
            }
        }
        alreadyIn.sort((o1, o2) -> Long.compare(o1.getBeginTime(), o2.getBeginTime()));
        return alreadyIn;
    }

    /**
     * 活动安排
     * @param userAccount 用户账号
     * @param weekNum 从2020.1.6(周一) 开始的周数
     * @param algorithmName
     * @return status,msg
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public LinkedHashMap<String, Object> eventManage(String userAccount, long weekNum, String algorithmName) {
        //课程表 以周一起始点为单位
        List<Lesson> lessons=lessonMapper.getLessonsByUserAccount(userAccount);
        //固定活动表 以周一为单位
        List<FixedEvent> fixedEvents=fixedEventsMapper.getFixedEventsByUserAccountAndWeekNum(new FixedEvent(userAccount,weekNum));
        //本周需要安排的活动
        List<Task> tasks=taskMapper.getUnFinishedTasksByUserAccountAndWeekNum(new Task(userAccount,weekNum));
        //日常活动 以天为单位
        List<DailyActivity> dailyActivities=dailyActivityMapper.getDailyActivitiesByUserAccount(userAccount);
        //组装活动表
        List<FixedEvent> alreadyIn = getFixedEvents(
                weekNum,
                lessons,
                fixedEvents,
                dailyActivities
        );

        LinkedHashMap<String, Object> resultMap=new LinkedHashMap<>();
        LinkedHashMap<String, Object> params = new LinkedHashMap<>();
        params.put("fixedEvents",alreadyIn);
        params.put("tasks",tasks);
        params.put("algorithmName",algorithmName);
        LinkedHashMap<String, Object> returnResult =taskManageRemoteCallService.taskManage(params);
        Integer status= (Integer) returnResult.get("status");
        //尝试安排活动
        if(status.equals(200))
        {
            resultMap.put("status",200);
            resultMap.put("msg","安排成功");
        }
        else
        {
            //失败后，去掉日常活动再来一次
            returnResult =taskManageRemoteCallService.taskManage(params);
            status= (Integer) returnResult.get("status");
            if(status.equals(200))
            {
                addUnConflictDailyActivity(alreadyIn,dailyActivities,weekNum);
                resultMap.put("status",201);
                resultMap.put("msg","为了DeadLine牺牲一下作息时间,以后可不能这样哦");
            }
            else
            {
                return returnResult;
            }
        }
        List<FixedEvent> fixedEventArray = (List<FixedEvent>) returnResult.get("results");
        ObjectMapper objectMapper=new ObjectMapper();
        List<FixedEvent> fixedEventList=objectMapper.convertValue(fixedEventArray, new TypeReference<List<FixedEvent>>() {});
        //添加安排后的结果
        addManagedResults(fixedEventList,userAccount,weekNum);
        return resultMap;
    }

    /**
     * 将每日活动，每周活动，固定活动装入日常表中，等待用户修改
     *
     * @param userAccount 用户账号
     * @param weekNum     周数
     * @return status, msg
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public LinkedHashMap<String, Object> fillFixedEvents(String userAccount, long weekNum) {
        //课程表 以周一起始点为单位
        List<Lesson> lessons=lessonMapper.getLessonsByUserAccount(userAccount);
        //固定活动表 以周一为单位
        List<FixedEvent> fixedEvents=fixedEventsMapper.getFixedEventsByUserAccountAndWeekNum(new FixedEvent(userAccount,weekNum));
        //日常活动 以天为单位
        List<DailyActivity> dailyActivities=dailyActivityMapper.getDailyActivitiesByUserAccount(userAccount);
        //组装活动表
        List<FixedEvent> alreadyIn = getFixedEvents(
                weekNum,
                lessons,
                fixedEvents,
                dailyActivities
        );

        LinkedHashMap<String, Object> resultMap=new LinkedHashMap<>();
        //判断是否有冲突的活动
        for(int i=1;i<alreadyIn.size();i++)
        {
            if(alreadyIn.get(i).getBeginTime()<alreadyIn.get(i-1).getEndTime())
            {
                resultMap.put("status",500);
                resultMap.put("msg","活动冲突");
                resultMap.put("fixedEvent1",alreadyIn.get(i));
                resultMap.put("fixedEvent2",alreadyIn.get(i-1));
                return resultMap;
            }
        }
        //添加安排后的结果
        addManagedResults(alreadyIn,userAccount,weekNum);
        resultMap.put("status",200);
        resultMap.put("msg","活动装填成功");
        return resultMap;
    }

    /**
     * 将任务装入固定时间表中
     *
     * @param userAccount 用户账号
     * @param weekNum     周数
     * @return status, msg
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public LinkedHashMap<String, Object> fillTasks(String userAccount, long weekNum,String algorithmName) {
        //经过用户修改后的日常表
        List<FixedEvent> alreadyIn=resultMapper.getResultsByUserAccountAndWeekNum(new FixedEvent(userAccount,weekNum));
        //本周需要安排的活动
        List<Task> tasks=taskMapper.getUnFinishedTasksByUserAccountAndWeekNum(new Task(userAccount,weekNum));
        LinkedHashMap<String, Object> resultMap=new LinkedHashMap<>();

        LinkedHashMap<String, Object> params=new LinkedHashMap<>();
        params.put("fixedEvents",alreadyIn);
        params.put("tasks",tasks);
        params.put("algorithmName",algorithmName);
        LinkedHashMap<String, Object> returnResult =taskManageRemoteCallService.taskManage(params);
        Integer status=(Integer) returnResult.get("status");
        //尝试安排活动
        if(status.equals(200))
        {
            resultMap.put("status",200);
            resultMap.put("msg","安排成功");
            List<FixedEvent> resultList=new ObjectMapper()
                    .convertValue(returnResult.get("results"), new TypeReference<List<FixedEvent>>() {});
            //添加安排后的结果
            addManagedResults(resultList,userAccount,weekNum);
        }
        else
        {
            resultMap.put("status",500);
            resultMap.put("msg","时间太紧张了，没法安排呢");
        }
        return resultMap;
    }

    /**
     * 计算间隔
     * @param results 活动表
     * @return 时间间隔
     */
    Long maxIntervalTime(List<FixedEvent> results)
    {
        long maxIntervalTime= -1L;
        for(int i=0;i+1<results.size();i++)
        {
            maxIntervalTime=Math.max(maxIntervalTime,
                    results.get(i+1).getBeginTime()-results.get(i).getEndTime());
        }
        return maxIntervalTime;
    }

    /**
     * 结合固定活动表所得的最大时间间隔
     *
     * @param userAccount 用户账号
     * @param weekNum     周数
     * @return 最大时间间隔
     */
    @Override
    public Long maxTaskTimeFromFixedEvents(String userAccount, long weekNum) {
        //课程表 以周一起始点为单位
        List<Lesson> lessons=lessonMapper.getLessonsByUserAccount(userAccount);
        //固定活动表 以周一为单位
        List<FixedEvent> fixedEvents=fixedEventsMapper.getFixedEventsByUserAccountAndWeekNum(new FixedEvent(userAccount,weekNum));
        //日常活动 以天为单位
        List<DailyActivity> dailyActivities=dailyActivityMapper.getDailyActivitiesByUserAccount(userAccount);
        //组装活动表
        List<FixedEvent> alreadyIn = getFixedEvents(
                weekNum,
                lessons,
                fixedEvents,
                dailyActivities
        );
        return maxIntervalTime(alreadyIn);
    }

    /**
     * 忽略日常活动(睡觉除外)后所得的最大时间间隔
     *
     * @param userAccount 用户账号
     * @param weekNum     周数
     * @return 最大时间间隔
     */
    @Override
    public Long maxTaskIgnoreDailyActivities(String userAccount, long weekNum) {
        //课程表 以周一起始点为单位
        List<Lesson> lessons=lessonMapper.getLessonsByUserAccount(userAccount);
        //固定活动表 以周一为单位
        List<FixedEvent> fixedEvents=fixedEventsMapper.getFixedEventsByUserAccountAndWeekNum(new FixedEvent(userAccount,weekNum));
        //日常活动 以天为单位
        List<DailyActivity> dailyActivities=dailyActivityMapper.getDailyActivitiesByUserAccount(userAccount);
        //组装活动表
        List<FixedEvent> alreadyIn = getFixedEventsIgnoreDailyActivity(
                weekNum,
                lessons,
                fixedEvents,
                dailyActivities
        );
        return maxIntervalTime(alreadyIn);
    }

    /**
     * 添加安排后的结果，会先清空result表，然后再添加
     * @param alreadyIn 安排后的时间表
     * @param userAccount 用户账号
     * @param weekNum 周数
     */
    void addManagedResults(List<FixedEvent> alreadyIn,String userAccount,long weekNum)
    {
        resultMapper.clearResultsByUserAccountAndWeekNum(new FixedEvent(userAccount,weekNum));
        System.out.println(alreadyIn);
        for(FixedEvent fixedEvent1:alreadyIn)
        {
            fixedEvent1.setUserAccount(userAccount);
        }
        resultMapper.addFixedEventByBatch(alreadyIn);
    }

    /**
     * 将不冲突的每日活动放入计划表中
     * @param alreadyIn 忽略日常活动后安排的计划表
     * @param dailyActivities 每日活动表
     * @param weekNum 周数
     */
    void addUnConflictDailyActivity(List<FixedEvent> alreadyIn,List<DailyActivity> dailyActivities,long weekNum)
    {
        for(DailyActivity dailyActivity:dailyActivities)
        {
            for(int day=WEEKBEGIN;day<=WEEKEND;day++) {
                FixedEvent fixedEvent = new FixedEvent(dailyActivity, weekNum,day);
                int l=-1;
                int r=alreadyIn.size();
                while(l<r)
                {
                    int mid=l+r+1>>1;
                    if(alreadyIn.get(mid).getEndTime()<=fixedEvent.getBeginTime())
                    {
                        l=mid;
                    }
                    else {
                        r=mid-1;
                    }
                }
                if(l+1==alreadyIn.size())
                {
                    alreadyIn.add(fixedEvent);
                }
                else if(alreadyIn.get(l+1).getBeginTime()>=fixedEvent.getEndTime())
                {
                    alreadyIn.add(l+1,fixedEvent);
                }
            }
        }
    }
}
